package org.gdstash.db;

import org.gdstash.file.ARZRecord;
import org.gdstash.util.GDMsgFormatter;
import org.gdstash.util.GDMsgLogger;

import java.sql.*;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

public class DBAffixSet {
    private static ConcurrentHashMap<String, DBAffixSet> hashBuffer;
    public static final String TABLE_NAME = "GD_AFFIXSET";
    private static final int ROW_AFFIXSET_ID = 1;
    private static final int ROW_INDEX = 2;
    private static final int ROW_LEVEL_MIN = 3;
    private static final int ROW_LEVEL_MAX = 4;
    private static final int ROW_AFFIX_ID = 5;
    protected String affixSetID;
    protected List<DBEntry> affixes;

    static {
        DBAffixSet.hashBuffer = new ConcurrentHashMap<String, DBAffixSet>();
    }

    protected List<DBAffix> affixList;

    public DBAffixSet() {
        this.affixes = new LinkedList<DBEntry>();
        this.affixList = new LinkedList<DBAffix>();
    }

    private DBAffixSet(final ARZRecord record) {
        this.affixSetID = record.getFileName();
        this.affixes = record.getAffixSetRandomizerList();
        this.affixList = this.determineAffixList();
    }

    private static List<DBAffixSet> wrap(final ResultSet rs) throws SQLException {
        final LinkedList<DBAffixSet> list = new LinkedList<DBAffixSet>();
        boolean found = false;
        while (rs.next()) {
            DBAffixSet set = null;
            final DBEntry entry = new DBEntry();
            final String id = rs.getString(1);
            entry.index = rs.getInt(2);
            entry.levelMin = rs.getInt(3);
            entry.levelMax = rs.getInt(4);
            entry.affixID = rs.getString(5);
            found = false;
            for (final DBAffixSet as : list) {
                if (as != null && as.affixSetID != null && as.affixSetID.equals(id)) {
                    found = true;
                    as.affixes.add(entry);
                    break;
                }
            }
            if (!found) {
                set = new DBAffixSet();
                set.affixSetID = id;
                set.affixes.add(entry);
                list.add(set);
            }
        }
        for (final DBAffixSet as2 : list) {
            as2.affixList = as2.determineAffixList();
        }
        return list;
    }

    public String getAffixSetID() {
        return this.affixSetID;
    }

    public static void clearBuffer() {
        DBAffixSet.hashBuffer.clear();
    }

    public static void createTables() throws SQLException {
        final String dropTable = "DROP TABLE GD_AFFIXSET";
        final String createTable = "CREATE TABLE GD_AFFIXSET (AFFIXSET_ID VARCHAR(256) NOT NULL, INDEX       INTEGER NOT NULL, LEVEL_MIN   INTEGER, LEVEL_MAX   INTEGER, AFFIX_ID    VARCHAR(256) NOT NULL, PRIMARY KEY (AFFIXSET_ID, INDEX))";
        try (final Connection conn = GDDBData.getConnection()) {
            final boolean auto = conn.getAutoCommit();
            conn.setAutoCommit(false);
            try (final Statement st = conn.createStatement()) {
                if (GDDBUtil.tableExists(conn, "GD_AFFIXSET")) {
                    st.execute(dropTable);
                }
                st.execute(createTable);
                st.close();
                conn.commit();
            } catch (SQLException ex) {
                conn.rollback();
                final Object[] args = {"GD_AFFIXSET"};
                final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_CREATE_TABLE", args);
                GDMsgLogger.addError(msg);
                throw ex;
            } finally {
                conn.setAutoCommit(auto);
            }
        }
    }

    public static void delete(final String affixSetID) throws SQLException {
        final String deleteEntry = "DELETE FROM GD_AFFIXSET WHERE AFFIXSET_ID = ?";
        try (final Connection conn = GDDBData.getConnection()) {
            final boolean auto = conn.getAutoCommit();
            conn.setAutoCommit(false);
            try (final PreparedStatement ps = conn.prepareStatement(deleteEntry)) {
                ps.setString(1, affixSetID);
                ps.executeUpdate();
                ps.close();
                conn.commit();
            } catch (SQLException ex) {
                conn.rollback();
                final Object[] args = {affixSetID, "GD_AFFIXSET"};
                final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_DEL_TABLE_BY_ID", args);
                GDMsgLogger.addError(msg);
                GDMsgLogger.addError(ex);
                throw ex;
            }
        }
    }

    public static void insert(final ARZRecord record) throws SQLException {
        final DBAffixSet entry = get(record.getFileName());
        if (entry != null) {
            return;
        }
        final DBAffixSet affixSet = new DBAffixSet(record);
        if (affixSet.affixes == null) {
            return;
        }
        if (affixSet.affixes.isEmpty()) {
            return;
        }
        final String insert = "INSERT INTO GD_AFFIXSET VALUES (?,?,?,?,?)";
        try (final Connection conn = GDDBData.getConnection()) {
            final boolean auto = conn.getAutoCommit();
            conn.setAutoCommit(false);
            try (final PreparedStatement ps = conn.prepareStatement(insert)) {
                for (final DBEntry dbEntry : affixSet.affixes) {
                    if (dbEntry.affixID != null) {
                        ps.setString(1, affixSet.affixSetID);
                        ps.setInt(2, dbEntry.index);
                        ps.setInt(3, dbEntry.levelMin);
                        ps.setInt(4, dbEntry.levelMax);
                        ps.setString(5, dbEntry.affixID);
                        ps.executeUpdate();
                        ps.clearParameters();
                    }
                }
                ps.close();
                conn.commit();
            } catch (SQLException ex) {
                conn.rollback();
                final Object[] args = {record.getFileName(), "GD_AFFIXSET"};
                final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_INS_TABLE_BY_ID", args);
                GDMsgLogger.addLowError(msg);
                GDMsgLogger.addLowError(ex);
            } finally {
                conn.setAutoCommit(auto);
            }
        }
    }

    public static DBAffixSet get(final String affixSetID) {
        DBAffixSet set = null;
        set = DBAffixSet.hashBuffer.get(affixSetID);
        if (set == null) {
            set = getDB(affixSetID);
            if (set != null) {
                DBAffixSet.hashBuffer.put(set.affixSetID, set);
            }
        }
        return set;
    }

    private static DBAffixSet getDB(final String affixSetID) {
        DBAffixSet set = null;
        final String command = "SELECT * FROM GD_AFFIXSET WHERE AFFIXSET_ID = ?";
        try (final Connection conn = GDDBData.getConnection();
             final PreparedStatement ps = conn.prepareStatement(command)) {
            ps.setString(1, affixSetID);
            try (final ResultSet rs = ps.executeQuery()) {
                final List<DBAffixSet> list = wrap(rs);
                if (list.isEmpty()) {
                    set = null;
                } else {
                    set = list.get(0);
                }
                conn.commit();
            } catch (SQLException ex) {
                throw ex;
            }
        } catch (SQLException ex2) {
            final Object[] args = {affixSetID, "GD_AFFIXSET"};
            final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_READ_TABLE_BY_ID", args);
            GDMsgLogger.addError(msg);
            GDMsgLogger.addError(ex2);
        }
        return set;
    }

    public static List<DBAffixSet> getByAffixSetIDs(final List<String> affixSetIDs) {
        final List<DBAffixSet> list = new LinkedList<DBAffixSet>();
        for (final String affixSetID : affixSetIDs) {
            final DBAffixSet set = get(affixSetID);
            if (set != null) {
                list.add(set);
            }
        }
        return list;
    }

    public static List<DBAffixSet> getCompletionAffixes() {
        List<DBAffixSet> list = null;
        final String command = "SELECT * FROM GD_AFFIXSET WHERE AFFIXSET_ID LIKE 'records/items/lootaffixes/completion%'";
        try (final Connection conn = GDDBData.getConnection();
             final PreparedStatement ps = conn.prepareStatement(command)) {
            try (final ResultSet rs = ps.executeQuery()) {
                list = wrap(rs);
                conn.commit();
            } catch (SQLException ex) {
                throw ex;
            }
        } catch (SQLException ex2) {
            GDMsgLogger.addError(GDMsgFormatter.getString(GDMsgFormatter.rbMsg, "ERR_READ_AFFIXSET_CRAFT"));
            GDMsgLogger.addError(ex2);
        }
        return list;
    }

    public static List<DBAffixSet> getCraftingAffixes() {
        List<DBAffixSet> list = null;
        final String command = "SELECT * FROM GD_AFFIXSET WHERE AFFIXSET_ID LIKE 'records/items/lootaffixes/crafting%'";
        try (final Connection conn = GDDBData.getConnection();
             final PreparedStatement ps = conn.prepareStatement(command)) {
            try (final ResultSet rs = ps.executeQuery()) {
                list = wrap(rs);
                conn.commit();
            } catch (SQLException ex) {
                throw ex;
            }
        } catch (SQLException ex2) {
            GDMsgLogger.addError(GDMsgFormatter.getString(GDMsgFormatter.rbMsg, "ERR_READ_AFFIXSET_CRAFT"));
            GDMsgLogger.addError(ex2);
        }
        return list;
    }

    public List<DBEntry> getAffixEntries() {
        return this.affixes;
    }

    public List<String> getAffixIDs() {
        final List<String> list = new LinkedList<String>();
        if (this.affixes == null) {
            return list;
        }
        for (final DBEntry entry : this.affixes) {
            if (entry != null && entry.affixID != null) {
                list.add(entry.affixID);
            }
        }
        return list;
    }

    public List<DBAffix> getAffixList() {
        return this.affixList;
    }

    public List<DBAffix> determineAffixList() {
        List<DBAffix> list = new LinkedList<DBAffix>();
        final List<String> ids = this.getAffixIDs();
        if (!ids.isEmpty()) {
            list = DBAffix.getByAffixSetID(ids);
        }
        return list;
    }

    public static class DBEntry {
        private int index;
        private int levelMin;
        private int levelMax;
        private String affixID;

        public int getIndex() {
            return this.index;
        }

        public int getMinLevel() {
            return this.levelMin;
        }

        public int getMaxLevel() {
            return this.levelMax;
        }

        public String getAffixID() {
            return this.affixID;
        }

        public void setIndex(final int index) {
            this.index = index;
        }

        public void setMinLevel(final int levelMin) {
            this.levelMin = levelMin;
        }

        public void setMaxLevel(final int levelMax) {
            this.levelMax = levelMax;
        }

        public void setAffixID(final String affixID) {
            this.affixID = affixID;
        }
    }
}
